package org.base.dao.modules.base;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.session.SqlSession;
import org.base.dao.common.Page;
import org.base.dao.common.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.entity.TableFieldInfo;
import com.baomidou.mybatisplus.entity.TableInfo;
import com.baomidou.mybatisplus.enums.SqlMethod;
import com.baomidou.mybatisplus.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.SqlHelper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.toolkit.MapUtils;
import com.baomidou.mybatisplus.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.toolkit.StringUtils;
import com.baomidou.mybatisplus.toolkit.TableInfoHelper;

public class BaseServiceImpl<T,D extends BaseDao<T>> implements BaseService<T> {
	
	public static final Logger log = LoggerFactory.getLogger(BaseServiceImpl.class);
	
	@Autowired
	protected D baseDao;
	
	@Override
	public Page<T> findPage(Page<T> page) {
		Map<String,Object> condition = page.getCondition();
		if(Objects.isNull(condition))
			condition = new HashMap<>();
			page.setCondition(condition);
		if(StringUtils.isNotEmpty(page.getOrderBy())) {
			if(condition.containsKey("orderBy"))
				log.info("the condition contain the illegal key orderBy,it will be cover");
			condition.put("orderBy", page.getOrderBy());
			if(condition.containsKey("sortAsc"))
				log.info("the condition contain the illegal key sortAsc,it will be cover");
			condition.put("sortAsc", page.isSortAsc());
		}
		List<T> records = baseDao.findList(page, condition);
		page.setRecords(records);
		return page;
	}
	
	@Override
	public List<T> findList(Map<String, Object> condition) {
		return baseDao.findList(condition);
	}
	
	@Override
	public List<T> findListByPairs(Pair... pairs) {
		Map<String,Object> condition = new HashMap<>();
		for(Pair p : pairs) {
			condition.put(p.getKey(), p.getValue());
		}
		return baseDao.findList(condition);
	}

	@Override
	public T findByPairs(Pair... pairs) {
		List<T> records = findListByPairs(pairs);
		if(records.size()<=0)
			return null;
		if(records.size()>1)
			log.info("the records is more than one, others wil be ignore");
		return records.get(0);
	}

	@Override
	public T findById(Long id) {
		return baseDao.findById(id);
	}

	@Override
	public T getOne(T entity) {
		return baseDao.selectOne(entity);
	}
	
	@Override
	public T getOne(Map<String, Object> data) {
		Wrapper<T> wrapper = mapEqWrapper(tableInfo(), data);
		List<T> entitys = baseDao.selectList(wrapper);
		if(CollectionUtils.isEmpty(entitys))
			return null;
		if(entitys.size()>1)
			log.info("The size of resultSet is more than 1");
		return entitys.get(0);
	}

	@Override
	public T getById(Long id) {
		return baseDao.selectById(id);
	}
	
	@Override
	public List<Map<String, Object>> getMaps(Map<String, Object> data) {
		return baseDao.selectMaps(mapEqWrapper(tableInfo(), data));
	}

	@Override
	public List<T> getListByIds(Collection<Long> ids) {
		String id = tableInfo().getKeyColumn();
		return baseDao.selectList(new EntityWrapper<T>().in(id, ids));
	}

	@Override
	public List<T> getList(Map<String, Object> condition) {
		TableInfo tableInfo = tableInfo();
		Wrapper<T> wrapper = mapEqWrapper(tableInfo, condition);
		return baseDao.selectList(wrapper);
	}

	@Override
	public List<T> getAll() {
		return baseDao.selectList(new EntityWrapper<T>());
	}
	
	@Override
	public Integer getCount(Map<String, Object> data) {
		return baseDao.selectCount(mapEqWrapper(tableInfo(), data));
	}

	@Override
	public boolean save(T entity) {
		return SqlHelper.retBool(baseDao.insert(entity));
	}

	@Override
	public boolean save(Map<String, Object> data) {
		try {
			T entity = currentModelClass().newInstance();
			BeanUtils.populate(entity, data);
			return this.save(entity);
		} catch (Exception e) {
			return false;
		}
	}

	@Override
	public boolean saveBatch(Collection<T> entitys) {
		if(CollectionUtils.isEmpty(entitys)) return false;
		try(SqlSession session = SqlSessionBatch()){
			String sql = tableInfo().getSqlStatement(SqlMethod.INSERT_ONE.getMethod());
			for(T entity : entitys) {
				session.insert(sql, entity);
			}
			session.flushStatements();
		}
		return true;
	}
	
	@Override
	public boolean saveBatchMap(Collection<Map<String, Object>> datas) {
		List<T> entitys = new ArrayList<>();
		Class<T> clazz = currentModelClass();
		for(Map<String,Object> data : datas) {
			try {
				T entity = clazz.newInstance();
				BeanUtils.populate(entity, data);
				entitys.add(entity);
			} catch (Exception e) {
				throw new MybatisPlusException(e);
			}
		}
		return this.saveBatch(entitys);
	}
	
	@Override
	public boolean updateById(Map<String, Object> data) {
		try {
			T entity = currentModelClass().newInstance();
			BeanUtils.populate(entity, data);
			return SqlHelper.retBool(baseDao.updateById(entity));
		} catch (Exception e) {
			return false;
		}
	}

	@Override
	public boolean updateById(T entity) {
		return SqlHelper.retBool(baseDao.updateById(entity));
	}

	@Override
	public boolean updateBatchById(Collection<T> entitys) {
		if(CollectionUtils.isEmpty(entitys)) return false;
		try(SqlSession session = SqlSessionBatch()){
			String sql = tableInfo().getSqlStatement(SqlMethod.UPDATE_BY_ID.getMethod());
			for(T entity : entitys) {
				MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
				param.put("et", entity);
				session.update(sql, param);
			}
			session.flushStatements();
		}
		return true;
	}
	
	@Override
	public boolean updateBatchMapById(Collection<Map<String, Object>> datas) {
		if(CollectionUtils.isEmpty(datas)) return false;
		try(SqlSession session = SqlSessionBatch()){
			String sql = tableInfo().getSqlStatement(SqlMethod.UPDATE_BY_ID.getMethod());
			for(Map<String, Object> data : datas) {
				MapperMethod.ParamMap<Map<String,Object>> param = new MapperMethod.ParamMap<>();
				param.put("et", data);
				session.update(sql, param);
			}
			session.flushStatements();
		}
		return true;
	}

	@Override
	public boolean removeById(Long id) {
		return SqlHelper.delBool(baseDao.deleteById(id));
	}

	@Override
	public boolean removeByIds(Collection<Long> ids) {
		return SqlHelper.delBool(baseDao.deleteBatchIds(ids));
	}

	@Override
	public boolean removeByMap(Map<String, Object> column) {
		return SqlHelper.delBool(baseDao.delete(mapEqWrapper(tableInfo(), column)));
	}
	
	@Override
	public boolean remove(T entity) {
		List<Field> fields = TableInfoHelper.getAllFields(currentModelClass());
		Wrapper<T> wrapper = new EntityWrapper<>();
		for(Field field : fields) {
			try {
				field.setAccessible(true);
				Object value = field.get(entity);
				if(Objects.isNull(value)) continue;
				TableField tableField = field.getAnnotation(TableField.class);
				String column = tableField.value();
				if(StringUtils.isEmpty(column))
					column = field.getName();
				wrapper.eq(column, value);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return SqlHelper.delBool(baseDao.delete(wrapper));
	}
	
	private SqlSession SqlSessionBatch() {
		return SqlHelper.sqlSessionBatch(currentModelClass());
	}
	
	public <E> Wrapper<E> mapEqWrapper(TableInfo tableInfo, Map<String, Object> column){
		Wrapper<E> wrapper = new EntityWrapper<>();
		if(MapUtils.isEmpty(column))
			return wrapper;
		for(String key : column.keySet()) {
			String columnName = columnName(tableInfo, key);
			if(StringUtils.isEmpty(columnName)) continue;
			wrapper.eq(columnName, column.get(key));
		}
		return wrapper;
	};
	
	protected String columnName(TableInfo tableInfo, String property) {
		List<TableFieldInfo> fields = tableInfo.getFieldList();
		for(TableFieldInfo field : fields) {
			if(field.getProperty().equals(property)) {
				return field.getColumn();
			}
		}
		return null;
	} 
	
	private TableInfo tableInfo() {
		return TableInfoHelper.getTableInfo(currentModelClass());
	}
	
    @SuppressWarnings("unchecked")
	private Class<T> currentModelClass() {
        return ReflectionKit.getSuperClassGenricType(getClass(), 0);
    }

}
